// Archivo de Configuracion de Tablas

/** \file tpy_tables.prg
 *  \brief Funciones de manipulacion de tablas.
 *  Estas funciones han sido creadas con la intención de traducir estos valores para 
 *  ser luego procesados en el motor de base de datos.
 *
 * \todo De momento, el trabajo se esta haciendo utilizando TDolpohin (MySQL) 
 *  pero la intención es hacerlo independiente del motor de base de datos.
 */

#include "tdolphin.ch"  // Esto cambiara a un valor universal..
#include "common.ch"

#define DIRSEP  hb_OSPathSeparator()

/** \brief Retorna array  */
function tpy_TablesStruct()
   local aListTables := tpy_TabDirList()
   local cTable, aTables := {}
   
   AEVAL( aListTables, {|cTable| tpy_TableStruct( cTable, aTables ) } )
      
return aTables


/** \brief Crea una estructura de tabla para dolphin.
 *  Lee el archivo <b>.ini</b> indicado en <i>cTable</i> e ingresa los valores
 *  en el array <i>aTablas</i>. 
 */
function tpy_TableStruct( cTable, aTablas )
   local oTabla, aMD5:={}, cCode
   local aIni, cSection, cKey, aSect, aField
   local aTabla,field,aItem,aItems,aAux
   local aPrimary, lPrimary, lIndexes, lFields
   local lForeigns
   local aFields:={},aIndexes:={},aForeigns:={}

   aIni := hb_IniRead( cTable )
   
   Default aTablas TO {}
  
   if Empty( aIni ) .or. !File( cTable )
      ? "Not a valid .ini file!"
   else
      lIndexes  := .f.
      
      FOR EACH cSection IN aIni:Keys
         //? "Section [" + cSection + "]"
         aSect := aIni[ cSection ]

         if cSection = "MAIN"
         
            aTabla := { &( aSect["TableName"] ), ;
                        &( aSect["Engine"   ] ), ;
                        &( aSect["CharSet"  ] ), ;
                        IIF( !Empty(aSect["Plugin"]), &( aSect["Plugin"   ] ), "") }
//                        &( aSect["Plugin"   ] ) }


            lPrimary := .f.
            aPrimary := { AllTrim( aTabla[1] )+"_pk", {{}, "primary" } }

         else
                     
            aItems := {}
            
            lFields:=.f. ; lIndexes:=.f.; lForeigns:=.f.
                  
            FOR EACH cKey IN aSect:Keys
               if cSection = "fields"
                  aAux  := &( "{" + aSect[cKey] + "}" )
                  aItem := tpy_Ini2DB( cKey, aAux ) 
                  
                  // Verificamos si el campo forma parte de llave primaria.
                  if !hb_IsNil( aAux[5] ) .AND. aAux[5] 
                     AADD( aPrimary[2,1], cKey )
                     lPrimary := .t.
                  endif
                  
                  AADD( aItems, aItem )
                  lFields:=.t.
                  
               endif
               
               if cSection = "indexes"
                  aItems := ARRAY(2)
                  aItems[1] := cKey
                  aItems[2] := &( "{" + aSect[cKey] + "}" )
                  
                  lIndexes := .t.
               endif
               
               if cSection = "foreigns"
                  aItems := ARRAY(2)
                  aItems[1] := cKey
                  aItems[2] := &( "{" + aSect[cKey] + "}" )
                  
                  lForeigns := .t.
               endif

            NEXT
            
//            if !lIndexes
//               AADD( aTabla, aItems )
//            else
//               AADD( aTabla, { aPrimary, aItems } )
//               lIndexes := .f.
//            endif
            
            if lFields
               aFields := aItems
            elseif lIndexes
               if lPrimary
                  aIndexes := { aPrimary, aItems }
               else
                  aIndexes := aItems
               endif
            elseif lForeigns
               aForeigns := { aItems }
            endif

         endif

      NEXT

      if !Empty(aTabla[4])
        //? "Aca debo ejecutar nuevamente para tomar las modificaciones de la tabla ",aTabla[1]
        aAux:={}
        //? AllTrim(aTabla[4])+".plugin"
        tpy_TableStruct( AllTrim(aTabla[4])+".plugin", aAux )
        // Verificamos si tiene campos...
        if !empty( aAux[1,2] )
           AEVAL( aAux[1,2], {|field,pos| AADD( aFields, field ) } )
//           ? ValToPrg( aFields )
        endif
        // Verificamos Indices...
        if !empty( aAux[1,3] )
           AEVAL( aAux[1,3], {|index,pos| AADD( aIndexes, index ) } )
        endif
        // Verificamos Foraneas...
        if !empty( aAux[1,4] )
           AEVAL( aAux[1,4], {|foreign,pos| AADD( aForeigns, foreign ) } )
        endif
      endif
      
      AADD( aTablas, { aTabla, aFields, aIndexes, aForeigns } )
      
   ENDIF
   
//  ?
//  ? ValToPrg( aTablas )
//? ValToPrg( {{"datos de la tabla"},aFields,aIndexes,aForeigns} )
//  ?
  
FOR EACH oTabla in aTablas

   //? hb_MD5( hb_serialize(oTabla:__enumValue[1]) )
   AADD( aMD5, { hb_MD5( hb_serialize(oTabla:__enumValue[1]) ),;
                 hb_MD5( hb_serialize(oTabla:__enumValue[2]) )} )
//   ? ValToPrg( oTabla:__enumValue[1] )

NEXT

//? ValToPrg( aMD5 )
return aTablas


/** \brief Retorna un array con la informacion de archivos.
 * Realiza la búsqueda de archivos en la ruta <i>cDir</i> filtrando por el valor 
 * indicado en <i>cFilter</i>
 * el valor predeterminado para <i>cFilter</i> es "*.ini"
 */
function tpy_TabDirList(cDir,cFilter)
   Local aDir, aFiles, cFullName
   
   Default cDir TO "tables"
   Default cFilter TO "*.ini"
   
   cFullName := cDir+DIRSEP+cFilter
   
   aDir := Directory( cFullName )
   
   aFiles := ARRAY( LEN(aDir) )
   
   AEVAL( aDir, { |file,nPos| aFiles[nPos] := cDir+DIRSEP+file[1] } )
   
return aFiles


/** \brief Verifica... (descripcion incompleta jeje )
 */
function tpy_CheckTable(aTable, aStruct, aIndices, aForeigns, aTriggers) // Triggers estan pendiente.
   local cDB := "tpy"
   local lCreate := .f.
   local cTabla
   local aIndice
   local aForeign
   
#define TAB_NAME     aTable[1]
#define TAB_ENGINE   aTable[2]
#define TAB_CHARSET  aTable[3]

   if Empty(aTable) .OR. Empty(aStruct) ; return lCreate ; endif
   
   oDbServer:SelectDB( cDB )
   
   cTabla := cDB+"."+TAB_NAME
   
   // Si es posible, modificar listtable para que permita el argumento esquema.  
//? "Existe la tabla "+cTabla+": ",oDbServer:TableExist(cTabla)
//AEVAL( MysqlListTbls( oDbServer:hMysql ), {|cTabla| QOUT(cTabla) } )
   
   if !oDbServer:TableExist(TAB_NAME)
   
      ? "Intentando Crear Tabla ["+TAB_NAME+"]"

      if !tpy_CheckFields( aStruct ) ; return .f. ; endif

//Metodo en Dolphin   
//CreateTable( cTable, aStruct, cPrimaryKey, cUniqueKey, cAuto, cExtra, lIfNotExist, lVer )
      if oDbServer:CreateTable( cTabla, aStruct, , , , ;
                                IIF( !EMPTY(TAB_CHARSET), "CHARSET="+TAB_CHARSET,NIL ) )
         oDbServer:ChangeEngine( cTabla, TAB_ENGINE )
         ? "Tabla '"+UPPER(TAB_NAME)+"' creada correctamente"
         ?
         if !Empty(aIndices)

//Metodo en Dolphin         
//CreateIndex( cName, cTable, aFNames, nCons, nType ) 
            FOR EACH aIndice IN aIndices
               oDbServer:CreateIndex( aIndice[1],   ;
                                      cTabla,       ;
                                      aIndice[2,1], ;
                                      tpy_IndexType( aIndice[2,2] ) )
               ? "Indice ["+aIndice[1]+"] Creado. "
               ?
            NEXT 
         endif
         
         if !Empty(aForeigns)
         
//METHOD CreateForeign( cName, cTable, aIndColName, cTabReference, aIndColRef, ;
//                      lOnDelete, nActionDelete, lOnUpdate, nActionUpdate )         
            FOR EACH aForeign IN aForeigns
            ? ValToPrg(aForeign)
               oDbServer:CreateForeign( aForeign[1],    ;
                                      cTabla,           ;
                                      aForeign[2,1],    ;
                                      aForeign[2,2],    ;
                                      aForeign[2,3],    ;
                                      aForeign[2,4],    ;
                                      tpy_ForeignType(aForeign[2,5]),    ;
                                      aForeign[2,6],    ;
                                      tpy_ForeignType(aForeign[2,7]) )
               ? "Llave Foranea ["+aForeign[1]+"] Creada. "
               ?
            NEXT 
         endif
      endif
   
   else
      ?? oDbServer:CreateInfo(TAB_NAME)   
      ? "por desarrollar en caso de existir la tabla se debe chequear.."
   endif

return lCreate


/** \brief Verifica los valores de las columnas para una estructura de tabla.
 *  \return .t. si todo esta bien, de lo contrario .f.
 */ 
function tpy_CheckFields( aStruct )
   local oItem
   
#define FIELD_NAME      oItem:__enumValue[1]
#define FIELD_TYPE      oItem:__enumValue[2]
#define FIELD_LEN       oItem:__enumValue[3]
#define FIELD_DEC       oItem:__enumValue[4]
#define FIELD_PK        oItem:__enumValue[5]
#define FIELD_DEFAULT   oItem:__enumValue[6]

   FOR EACH oItem IN aStruct

      if FIELD_TYPE="C" .AND. LEN( FIELD_DEFAULT )> FIELD_LEN
         ? "ERROR: Campo ["+FIELD_NAME+"] con Longitud inválida."
         return .f.
      endif
      
      if FIELD_TYPE="N" .AND. VALTYPE( FIELD_DEFAULT ) = "C"
         ? "ERROR: Campo ["+FIELD_NAME+"] con Valor 'Default' Incoherente. "
         return .f.
      endif

   NEXT   
   
return .t.


/** \brief Retorna valor numerico correspondiente a tipo de indice.
 *  Si el valor <i>cType</i> no coindide, retorna 0.
 */
function tpy_IndexType(cType)
   local cIndex := lower(cType)
   local nIndex
   
   DO CASE
   CASE cType = "unique"
        nIndex := IDX_UNIQUE
   CASE cType = "primary"
        nIndex := IDX_PRIMARY
   CASE cType = "spatial"
        nIndex := IDX_SPATIAL
   CASE cType = "fulltext"
        nIndex := IDX_FULLTEXT
   OTHERWISE
        nIndex := 0
   ENDCASE
return nIndex


/** \brief retona valor numerico correspondiente al tipo de llave foranea.
  * si no coincide, retorna 4. <b>(no action)</b>
  */
function tpy_ForeignType(cType)
   local cIndex := lower(cType)
   local nIndex 
   
// { "RESTRICT", "CASCADE", "SET NULL", "NO ACTION" }
   DO CASE
   CASE cType = "restrict"
        nIndex := 1
   CASE cType = "cascade"
        nIndex := 2
   CASE cType = "set null"
        nIndex := 3
   CASE cType = "no action"
        nIndex := 4
   OTHERWISE
        nIndex := 4
   ENDCASE
return nIndex


/** \brief crea la compatibilidad de un valor para ser trasladado a la Base de Datos */
function tpy_Ini2DB( cField, aField )
   local aItem := {}
   local field
   
   AADD( aItem, cField )

   // Si el default esta vacio y el campo no acepta nulo... inicializamos un valor.
   if afield[1]="C" .and. aField[4] .and. empty( afield[6] )
      afield[6]:="*"
   endif

   // Para hacer compatible la estructura con Dolphin,
   // no pasamos el valor que indica si el campo es "primary" [5]
   FOR EACH field IN aField

      if field:__enumIndex != 5
         AADD(aItem, field:__enumValue )
      endif
      
   NEXT
return aItem

//eof
